﻿using System;
using System.Collections.Generic;
using System.IO;
using Index;
using Index.Interfaces;
using Index.TreeIndex;

namespace IndexTest
{
    public class TreeIndexTierTest : ITest
    {
        public TreeIndexTierTest()
        {
            this.FailureDetails = new List<string>();
        }

        ITreeIndexTier [] tiers = new ITreeIndexTier[8];

        private void Cleanup(string indexDirectory, string indexName)
        {
            if (Directory.Exists(indexDirectory))
            {
                string[] files = Directory.GetFiles(indexDirectory, string.Format("{0}-*.index", indexName));

                foreach (string file in files)
                {
                    File.Delete(file);
                }
            }
            else
            {
                Directory.CreateDirectory(indexDirectory);
            }
        }

        #region ITest Members

        public bool Execute()
        {
            try
            {
                string indexDirectory = "BIndex";
                string indexName1 = "index1";
                string indexName2 = "index2";

                Cleanup(indexDirectory, indexName1);
                Cleanup(indexDirectory, indexName2);


                // SEQUENTIAL IDs
                InitializeTiers(indexDirectory, indexName1);

                uint value1 = 0x05050505;
                uint value2 = 0x04040404;
                uint value3 = 0x06060606;

                uint id1 = 0x12345678;
                uint id2 = 0x12345677;
                uint id3 = 0x12345679;

                AddValue(id1, value1);
                AddValue(id2, value2);
                AddValue(id3, value3);

                uint foundValue1 = FindValue(id1);
                uint foundValue2 = FindValue(id2);
                uint foundValue3 = FindValue(id3);

                if(foundValue1 != value1 || 
                    foundValue2 != value2 ||
                    foundValue3 != value3)
                {
                    this.FailureDetails.Add("Read values don't match added values");
                    throw new Exception("Incorrect reading functionality");
                }

                uint count = 10000;

                DateTime start = DateTime.Now;
                Console.WriteLine("{0} sequential numbers start time: {1:HH:mm:ss}", this.DisplayName, start);

                for (uint k = 0; k < count; k++)
                {
                    if (k % 100000 == 0) Console.WriteLine(k);
                    
                    this.AddValue(k, k);
                }

                TimeSpan duration = DateTime.Now - start;
                Console.WriteLine("Duration: {0:hh\\:mm\\:ss\\.fff}", duration); //@"d\.hh\:mm\:ss"

                ClearTiers();



                // RANDOM IDs
                InitializeTiers(indexDirectory, indexName2);

                start = DateTime.Now;
                Console.WriteLine("{0} random numbers start time: {1:HH:mm:ss}", this.DisplayName, start);

                Random random = new Random();

                for(uint k = 0; k < count; k++)
                {
                    if(k % 100000 == 0) Console.WriteLine(k);
                    uint id = (uint) random.Next();
                    this.AddValue(id, k);
                }

                duration = DateTime.Now - start;
                Console.WriteLine("Duration: {0:hh\\:mm\\:ss\\.fff}", duration); //@"d\.hh\:mm\:ss"

                ClearTiers();

            }
            catch (Exception exception)
            {
                this.FailureDetails.Add("ERROR MESSAGE:\n" + exception.Message);
                this.FailureDetails.Add("STACK TRACE:\n" + exception.StackTrace);
                return false;
            }

            return true;
        }

        private void AddValue(uint id, uint value)
        {
            uint node = 0;

            if(!this.tiers[0].HasNode(node))
            {
                node = this.tiers[0].CreateNode();
            }

            AddToTier(node, new UintDigits(id), value);
        }

        private void AddToTier(uint node, UintDigits id, uint value)
        {
            ITreeIndexTier tier = this.tiers[id.Index];

            if(id.IsLastDigit)
            {
                tier.SetNextTierNodeOffset(node, id.CurrentDigit, value);
            }
            else
            {
                uint nextTierNode = tier.GetNextTierNodeOffset(node, id.CurrentDigit);

                if (nextTierNode == TreeIndexTier.EmptyValue)
                {
                    nextTierNode = this.tiers[id.Index + 1].CreateNode();
                    tier.SetNextTierNodeOffset(node, id.CurrentDigit, nextTierNode);
                }

                id.NextDigit();
                AddToTier(nextTierNode, id, value);
            }
        }

        private uint FindValue(uint id)
        {
            uint node = 0;

            if (!this.tiers[0].HasNode(node))
            {
                return TreeIndexTier.EmptyValue;
            }
            else
            {
                return FindInTier(node, new UintDigits(id));
            }
        }

        private uint FindInTier(uint node, UintDigits id)
        {
            ITreeIndexTier tier = this.tiers[id.Index];

            if(id.IsLastDigit)
            {
                return tier.GetNextTierNodeOffset(node, id.CurrentDigit);
            }
            else
            {
                uint nextTierNode = tier.GetNextTierNodeOffset(node, id.CurrentDigit);

                if(nextTierNode == TreeIndexTier.EmptyValue)
                {
                    return TreeIndexTier.EmptyValue;
                }

                id.NextDigit();
                return FindInTier(nextTierNode, id);
            }
        }

        public string DisplayName
        {
            get { return "TreeIndexTier Test"; }
        }

        public System.Collections.Generic.List<string> FailureDetails { get; set; }

        #endregion

        private void InitializeTiers(string indexDirectory, string indexName)
        {
            for (int k = 0; k < this.tiers.Length; k++ )
            {
                this.tiers[k] = new TreeIndexTier();
                this.tiers[k].Initialize(indexDirectory, indexName, k);
            }
        }

        private void ClearTiers()
        {
            for(int k = 0; k < this.tiers.Length; k++)
            {
                this.tiers[k].Cleanup();
            }
        }
    }
}